﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.ServiceHosting.ServiceRuntime;
using Microsoft.Samples.ServiceHosting.StorageClient;
using UlyssesAgenda.CoreLib;
using System.Collections.Specialized;
using UlyssesAgenda.GameEngine.Utilities;
using System.Collections;
using System.Reflection;
using UlyssesAgenda.Storage.Blob;

namespace UlyssesAgenda.GameEngine
{
    public class Engine : IGameEngine
    {
        private static DateTime started;
        private static BlobContainer gobContainer;
        private static BlobContainer regionContainer;
        private static BlobStorage blobStorage;
        private static StorageAccountInfo storageAccount;
        private static Dictionary<string, Assembly> regions;
        private static bool initialized = false;

        public static void Initialize()
        {
            if (initialized) return;

            started = DateTime.Now;
           
            storageAccount = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration();
            blobStorage = BlobStorage.Create(storageAccount);
            gobContainer = blobStorage.GetBlobContainer(RoleManager.GetConfigurationSetting("GameObjectsContainer"));
            regionContainer = blobStorage.GetBlobContainer(RoleManager.GetConfigurationSetting("RegionContainer"));
            gobContainer.CreateContainer(new NameValueCollection(), ContainerAccessControl.Public);
            regionContainer.CreateContainer(null, ContainerAccessControl.Public);

            LoadRegionsFromBlobStorage();
            initialized = true;
        }

        private static void LoadRegionsFromBlobStorage()
        {
            BlobProperties[] blobProperties = GameBlobStorage.GetBlobsInContainer(regionContainer);
            regions = new Dictionary<string, Assembly>();
            foreach (BlobProperties blobProps in blobProperties)
            {
                MemoryStream s = new MemoryStream();
                BlobContents contents = new BlobContents(s);
                regionContainer.GetBlob(blobProps.Name, contents, false);
                Assembly regionAssembly = Assembly.Load(contents.AsBytes());
                string modifiedName = blobProps.Name.Replace(".dll", string.Empty).Trim();
                regions.Add(modifiedName, regionAssembly);
            }
        }

        public static Dictionary<string, string> GetState(string canonicalName)
        {
            Initialize();
            BlobContents contents = new BlobContents(new MemoryStream());
            BlobProperties props = gobContainer.GetBlob(canonicalName, contents, false);

            byte[] blobBytes = contents.AsBytes();
            return StateUtility.GetStateFromBinary(blobBytes);
        }

        public static bool HasState(string canonicalName)
        {
            Initialize();
            return gobContainer.DoesBlobExist(canonicalName);            
        }

        public static void SaveState(string canonicalName, Dictionary<string, string> state)
        {
            if (!initialized) Initialize();
            BlobProperties properties = new BlobProperties(canonicalName);

            // Create metadata to be associated with the blob
            NameValueCollection metadata = new NameValueCollection();
            metadata["LastSave"] = DateTime.Now.ToUniversalTime().ToString();

            properties.Metadata = metadata;
            properties.ContentType = null;

            // Create the blob
            BlobContents stateBlob = new BlobContents(StateUtility.GetBinaryFromState(state));
            gobContainer.CreateBlob(properties, stateBlob, true);
        }

        private Assembly AssemblyForCanonicalName(string canonicalName)
        {
            foreach (string targetName in regions.Keys)
            {
                if (canonicalName.StartsWith(targetName))
                {
                    Assembly targetAssembly = regions[targetName];
                    return targetAssembly;
                }
            }
            return null;
        }

        private string TypeNameForCanonicalName(string canonicalName)
        {
            string typeName = canonicalName;
            if (canonicalName.Contains("$"))
                typeName = canonicalName.Substring(0, canonicalName.IndexOf("$"));

            return typeName;
        }


        #region IGameEngine Members

        public DateTime StartupTime
        {
            get { return started; }
        }

        public GameObject GetInstance(string canonicalName)
        {
            Initialize();

            // If the gob isn't being tracked, this method must return a null.
            if (!HasState(canonicalName)) return null;

            Assembly targetAssembly = AssemblyForCanonicalName(canonicalName);
            string typeName = TypeNameForCanonicalName(canonicalName);
            object gob = targetAssembly.CreateInstance(typeName);
            GameObject gameObject = gob as GameObject;
            if (gameObject != null)
            {
                gameObject.CanonicalName = canonicalName;
                gameObject.Engine = this;
                gameObject.LoadState();
            }
            return gameObject;            
        }

        public GameObject GetOrCreateInstance(string canonicalName)
        {
            Initialize();

            GameObject gob = GetInstance(canonicalName);
            if (gob == null)
            {
                gob = CreateInstance(canonicalName);
            }
            return gob;
        }

        public GameObject CreateInstance(string canonicalName)
        {
            string typeName = TypeNameForCanonicalName(canonicalName);
            Assembly targetAssembly = AssemblyForCanonicalName(canonicalName);
            Type t = targetAssembly.GetType(typeName);
            object[] attribs = t.GetCustomAttributes(typeof(SingletonAttribute), true);
            string newCanonicalName = canonicalName;
            GameObject gob = null;
            if ((attribs == null) || (attribs.Length == 0))
            {
                // this is not a singleton, so we need a GUID
                newCanonicalName += "$" + Guid.NewGuid().ToString("N");
                gob = (GameObject)targetAssembly.CreateInstance(typeName);
                gob.CanonicalName = newCanonicalName;
                gob.Engine = this;
                gob.SaveState();
            }
            else
            {
                // this is a singleton. Just return the existing object.
                if (HasState(canonicalName))
                {
                    gob = GetInstance(canonicalName);
                }
                else
                {
                    // This is an as-yet-untracked singleton. Save state and hand back.
                    gob = (GameObject)targetAssembly.CreateInstance(typeName);
                    gob.CanonicalName = newCanonicalName;
                    gob.Engine = this;
                    gob.SaveState();
                }
            }
            
            return gob;
        }

        public Dictionary<string, string> LoadState(string canonicalName)
        {
            return Engine.GetState(canonicalName);
        }

        public void SaveState(Dictionary<string, string> state, string canonicalName)
        {
            Engine.SaveState(canonicalName, state);
        }

        #endregion
    }
}
